/** @file    EncoderXML.cpp
 *  @author  Alessandro Polo
 *  @version $Id: EncoderXML.cpp 2047 2010-03-07 00:27:16Z alex $
 *  @brief
 * File containing methods for the wosh::database::EncoderXML class.
 * The header for this class can be found in EncoderXML.h, check that file
 * for class description.
 ****************************************************************************/
/* Copyright (c) 2007-2010, WOSH - Wide Open Smart Home 
 * by Alessandro Polo - OpenSmartHome.com
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the OpenSmartHome.com WOSH nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY Alessandro Polo ''AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL Alessandro Polo BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 ****************************************************************************/

 #include <framework/database/EncoderXML.h>

 #include <framework/database/EncoderXMLCommon.h>
 #include <core/Utilities.h>
 #include <core/SystemError.h>
 #include <core/FileSystem.h>
 #include <core/ObjectAllocator.h>

 #include <QFile>

 
using namespace std;
namespace wosh {
 namespace database {

 WOSH_REGISTER(wosh::database::EncoderXML, "wosh::DataBase", 1.10, _static_EncoderXML)

std::map<std::string, wosh::database::EncoderXML_base*> wosh::database::EncoderXML::objectRegs;
wosh::Mutex EncoderXML::objRegsMux;

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////// CONSTRUCTORS

EncoderXML::EncoderXML() : DataBase()
 {
	Object::setName( _DataBaseXML_NAME );

	Log.setContext( "EncoderXML" );
	Log.setIndent( 3 );
	Log.setLevel( LOG_INFO );
//Log.setLevel( LOG_VERBOSE );

	Log(LOG_INFO, " Registering Built-in Types.." );
	EncoderXML_register_default_types();
	Log(LOG_VERBOSE, " Registered %i Built-in Types..", EncoderXML::objectRegs.size() );

 }

EncoderXML::~EncoderXML()
 {
	Log(LOG_INFO, " Destroying.." );

	MutexLocker mL(&EncoderXML::objRegsMux);
	std::map<std::string,wosh::database::EncoderXML_base*>::iterator it;
	for ( it=EncoderXML::objectRegs.begin(); it != EncoderXML::objectRegs.end(); ++it ) {
		if ( it->second == NULL ) continue;
		delete it->second;
	}
	EncoderXML::objectRegs.clear();

	Log(LOG_VERBOSE, ":~EncoderXML() Destroyed." );
 }

//////////////////////////////////////////////////////////////////////////////////////////////// CONSTRUCTORS
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

WRESULT EncoderXML::registerDataType( const std::string& class_name, EncoderXML_base* object )
 {
	if ( object == NULL ) return WRET_ERR_PARAM;

	MutexLocker mL(&EncoderXML::objRegsMux);

	if ( EncoderXML::objectRegs.find(class_name) != EncoderXML::objectRegs.end() ) {
//		Log(LOG_WARNING, ":registerDataType() Overwriting datatype %s", class_name.c_str() );
		EncoderXML_base* oldObj = EncoderXML::objectRegs[class_name];
		if ( oldObj != NULL )
			delete oldObj;
		EncoderXML::objectRegs[class_name] = NULL;
	 }

	EncoderXML::objectRegs[ class_name ] = object;
//	Log(LOG_VERBOSE, ":registerDataType() Registered datatype %s", class_name.c_str() );

	return WRET_OK;
 }




//////////////////////////////////////////////////////////////////////////////////////////////////////////////

WRESULT EncoderXML::loadXmlDocument( QDomDocument& doc, const std::string& filename )
 {
	QFile file(filename.c_str());
	if ( !file.open(QIODevice::ReadOnly) ) {
		Log(LOG_CRITICAL, ":loadXmlDocument() FAILED : Can't open file: %s", filename.c_str() );
		return WRET_ERR_PARAM;
	 }
	if ( !doc.setContent(&file) ) {
		file.close();
		Log(LOG_CRITICAL, ":loadXmlDocument() FAILED : Can't set contents from: %s", filename.c_str() );
		return WRET_ERR_PARAM;
	 }
	file.close();

	QDomElement woshNode = doc.documentElement();
	if ( woshNode.isNull() ) {
		Log(LOG_WARNING, ":loadXmlDocument() FAILED : No root node in %s", filename.c_str() );
		return WRET_ERR_INTERNAL;
	 }

	return WRET_OK;
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

WRESULT EncoderXML::saveXmlDocument( QDomDocument& doc, const std::string& filename )
 {
	QFile File( filename.c_str() );
	if ( File.open( QIODevice::WriteOnly ) ) {
		QTextStream TextStream(&File);
		TextStream << doc.toString();
		File.close();
	 }
	else 
		Log(LOG_CRITICAL, ":saveXmlDocument() can't load file %s", filename.c_str() );

	return WRET_OK;
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

WRESULT EncoderXML::loadObjects( std::vector<ISerializable*>& objects, const std::string& class_name, const std::string& xml_source )
 {
	FilePath xmlFile;
	if ( !xmlFile.set( xml_source ) ) {
		Log(LOG_CRITICAL, ":loadObjects() FAILED resolving of %s", xml_source.c_str() );
		return WRET_ERR_PARAM;
	 }
	std::vector<std::string> sourceFiles;
	if ( xmlFile.isFolder() ) {
		WRESULT ret = FileSystem::getFilesOf( xmlFile.getPathLocal(), sourceFiles, true, true, ".xml" );
		if ( WFAILED(ret) || sourceFiles.empty() ) {
			Log(LOG_CRITICAL, ":loadObjects() FAILED getting files of %s", xmlFile.getPathLocal().c_str() );
			return WRET_ERR_PARAM;
		 }
		Log(LOG_VERBOSE, ":loadObjects() parsing %i files of folder %s", sourceFiles.size(), xmlFile.getPathLocal().c_str() );
	 }
	else if ( xmlFile.isFile() ) {
		sourceFiles.push_back( xmlFile.getPathLocal() );
	 }
	else {
		Log(LOG_CRITICAL, ":loadObjects() FAILED file %s doesn't exist!", xmlFile.getPathLocal().c_str() );
		return WRET_ERR_PARAM;
	 }

	std::string filename;
	std::vector<std::string>::iterator it;
	for ( it=sourceFiles.begin(); it!=sourceFiles.end(); ++it ) {
		filename = *it;

		QDomDocument doc("none");
		WRESULT valid = loadXmlDocument( doc, filename );
		if ( WFAILED(valid) ) {
			SystemError::raise(this, Log, SystemError::CRITICAL, 0, "loadObjects", "FAILED#1 : Cannot load document %s", filename.c_str() );
			continue;
		 }

		QDomNodeList nodesList;
		if ( class_name == "" )
			nodesList = doc.documentElement().childNodes();
		else
			nodesList = doc.documentElement().elementsByTagName( class_name.c_str() );
		int nodeCount = nodesList.length();

		if ( nodeCount == 0 ) {
			Log(LOG_CRITICAL, ":loadObjects() FAILED : No items found in %s", filename.c_str() );
			continue;
		 }
		Log(LOG_VERBOSE, ":loadObjects() loading %i items in %s", nodeCount, filename.c_str() );

		std::vector<std::string> classErrs;
		std::string tagname;
		for(int j=0; j<nodeCount; j++) {
			QDomElement child = nodesList.item(j).toElement();
			tagname = std::string( child.tagName().toLatin1().data() );
			if ( tagname == "" ) {
				Log(LOG_WARNING, ":loadObjects() #%i Empty TagName", j );
				continue;
			 }
			EncoderXML_base* obj_ser = EncoderXML::findEncoderFor(tagname);
			if ( obj_ser == NULL ) {
				classErrs.push_back(tagname);
				Log(LOG_CRITICAL, ":loadObjects() #%i FAILED Class not found '%s'", j, tagname.c_str() );
				continue;
			 }
			ISerializable* obj  = obj_ser->allocObject();

			WRESULT ret = obj_ser->deserializeFrom(obj, child);
			if ( WFAILED(ret) )
				Log(LOG_WARNING, ":loadObjects() #%i %s Deserialization-Error ret=%i", j, tagname.c_str(), ret );
			else
				Log(LOG_VERBOSE, ":loadObjects() #%i %s loaded.", j, tagname.c_str() );

			objects.push_back( obj );
		 }

		if ( !classErrs.empty() ) {
			std::string errs = Utilities::joinVector(classErrs, "; ");
			SystemError::raise(this, Log, SystemError::CRITICAL, 0, "loadObjects", "%i ERRORS: Classes not found: %s", classErrs.size(), errs.c_str() );
		 }
	 }

	return WRET_OK;
 }

EncoderXML_base* EncoderXML::findEncoderFor( const std::string& tagname )
 {
	if ( EncoderXML::objectRegs.find( tagname ) == EncoderXML::objectRegs.end() ) {
//		Log(LOG_CRITICAL, ":loadObjects() #%i FAILED Class not found '%s'", j, tagname.c_str() );
		return NULL;
	 }

	return EncoderXML::objectRegs[ tagname ];
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

WRESULT EncoderXML::saveObjects( std::vector<wosh::ISerializable*>& objects, const std::string& filename )
 {
	FilePath xmlFile;
	if ( !xmlFile.set( filename ) ) {
		Log(LOG_CRITICAL, ":saveObjects() FAILED resolving of %s", filename.c_str() );
		return WRET_ERR_PARAM;
	 }

	Log(LOG_VERBOSE, ":saveObjects() saving %i items in %s", objects.size(), xmlFile.getPathLocal().c_str() );

	QDomDocument doc("none");
	QDomElement rootNode = doc.createElement("WOSH");
	doc.appendChild(rootNode);

	std::vector<std::string> classErrs;
	int j = 0;
	std::vector<ISerializable*>::const_iterator it;
	for ( it=objects.begin(); it != objects.end(); ++it, ++j ) {
		if ( *it == NULL ) continue;
		
		if ( EncoderXML::objectRegs.find( std::string((*it)->getClassName()) ) == EncoderXML::objectRegs.end() ) {
			classErrs.push_back((*it)->getClassName());
			Log(LOG_CRITICAL, ":saveObjects() #%i Class not found '%s'", j, (*it)->getClassName() );
			continue;
		 }
		EncoderXML_base* obj_ser = EncoderXML::objectRegs[ std::string((*it)->getClassName()) ];
		QDomElement cNode = doc.createElement( (*it)->getClassName() );
		WRESULT ret = obj_ser->serializeTo( *it, doc, cNode, false );
		if ( WFAILED(ret) )
			Log(LOG_WARNING, ":saveObjects() #%i %s Error ret=%i", j, (*it)->getClassName(), ret );
		else
			Log(LOG_VERBOSE, ":saveObjects() #%i saved.", j );

		rootNode.appendChild( cNode );
	 }

	QFile File( xmlFile.getPathLocal().c_str() );
	if ( File.open( QIODevice::WriteOnly ) ) {
		QTextStream TextStream(&File);
		TextStream << doc.toString();
		File.close();
	 }
	else 
		wosh::SystemError::raise(this, Log, SystemError::CRITICAL, 0, "saveObjects", "FAILED#1 : Cannot write to file %s", xmlFile.getPathLocal().c_str() );

	if ( !classErrs.empty() ) {
		std::string errs = wosh::Utilities::joinVector(classErrs, "; ");
		SystemError::raise(this, Log, SystemError::CRITICAL, 0, "saveObjects", "%i ERRORS: Classes not found: %s", classErrs.size(), errs.c_str() );
	 }

	return WRET_OK;
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

 }; // namespace database
}; // namespace wosh
